home *** CD-ROM | disk | FTP | other *** search
/ Languguage OS 2 / Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO / gnu / cvs-1_3.lha / cvs-1.3 / src / rcs.c < prev    next >
C/C++ Source or Header  |  1992-03-31  |  34KB  |  1,450 lines

  1. /*
  2.  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  3.  * 
  4.  * You may distribute under the terms of the GNU General Public License as
  5.  * specified in the README file that comes with the CVS 1.3 kit.
  6.  * 
  7.  * The routines contained in this file do all the rcs file parsing and
  8.  * manipulation
  9.  */
  10.  
  11. #include "cvs.h"
  12.  
  13. #ifndef lint
  14. static char rcsid[] = "@(#)rcs.c 1.28 92/03/31";
  15. #endif
  16.  
  17. #if __STDC__
  18. static char *RCS_getbranch (RCSNode * rcs, char *tag, int force_tag_match);
  19. static char *RCS_getdatebranch (RCSNode * rcs, char *date, char *branch);
  20. static int getrcskey (FILE * fp, char **keyp, char **valp);
  21. static int parse_rcs_proc (Node * file);
  22. static int checkmagic_proc (Node *p);
  23. static void do_branches (List * list, char *val);
  24. static void do_symbols (List * list, char *val);
  25. static void null_delproc (Node * p);
  26. static void rcsnode_delproc (Node * p);
  27. static void rcsvers_delproc (Node * p);
  28. #else
  29. static int parse_rcs_proc ();
  30. static int checkmagic_proc ();
  31. static void rcsnode_delproc ();
  32. static void rcsvers_delproc ();
  33. static void null_delproc ();
  34. static int getrcskey ();
  35. static void do_symbols ();
  36. static void do_branches ();
  37. static char *RCS_getbranch ();
  38. static char *RCS_getdatebranch ();
  39. #endif                /* __STDC__ */
  40.  
  41. static List *rcslist;
  42. static char *repository;
  43.  
  44. /*
  45.  * Parse all the rcs files specified and return a list
  46.  */
  47. List *
  48. RCS_parsefiles (files, xrepos)
  49.     List *files;
  50.     char *xrepos;
  51. {
  52.     /* initialize */
  53.     repository = xrepos;
  54.     rcslist = getlist ();
  55.  
  56.     /* walk the list parsing files */
  57.     if (walklist (files, parse_rcs_proc) != 0)
  58.     {
  59.     /* free the list and return NULL on error */
  60.     dellist (&rcslist);
  61.     return ((List *) NULL);
  62.     }
  63.     else
  64.     /* return the list we built */
  65.     return (rcslist);
  66. }
  67.  
  68. /*
  69.  * Parse an rcs file into a node on the rcs list
  70.  */
  71. static int
  72. parse_rcs_proc (file)
  73.     Node *file;
  74. {
  75.     Node *p;
  76.     RCSNode *rdata;
  77.  
  78.     /* parse the rcs file into rdata */
  79.     rdata = RCS_parse (file->key, repository);
  80.  
  81.     /* if we got a valid RCSNode back, put it on the list */
  82.     if (rdata != (RCSNode *) NULL)
  83.     {
  84.     p = getnode ();
  85.     p->key = xstrdup (file->key);
  86.     p->delproc = rcsnode_delproc;
  87.     p->type = RCSNODE;
  88.     p->data = (char *) rdata;
  89.     (void) addnode (rcslist, p);
  90.     }
  91.     return (0);
  92. }
  93.  
  94. /*
  95.  * Parse an rcsfile given a user file name and a repository
  96.  */
  97. RCSNode *
  98. RCS_parse (file, repos)
  99.     char *file;
  100.     char *repos;
  101. {
  102.     RCSNode *rcs;
  103.     char rcsfile[PATH_MAX];
  104.  
  105.     (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
  106.     if (!isreadable (rcsfile))
  107.     {
  108.     (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC,
  109.             file, RCSEXT);
  110.     if (!isreadable (rcsfile))
  111.         return (NULL);
  112.     rcs = RCS_parsercsfile (rcsfile);
  113.     if (rcs != NULL)
  114.     {
  115.         rcs->flags |= INATTIC;
  116.         rcs->flags |= VALID;
  117.     }
  118.     return (rcs);
  119.     }
  120.     rcs = RCS_parsercsfile (rcsfile);
  121.     if (rcs != NULL)
  122.     rcs->flags |= VALID;
  123.     return (rcs);
  124. }
  125.  
  126. /*
  127.  * Do the real work of parsing an RCS file
  128.  */
  129. RCSNode *
  130. RCS_parsercsfile (rcsfile)
  131.     char *rcsfile;
  132. {
  133.     Node *q, *r;
  134.     RCSNode *rdata;
  135.     RCSVers *vnode;
  136.     int n;
  137.     char *cp;
  138.     char *key, *value;
  139.     FILE *fp;
  140.  
  141.     /* open the rcsfile */
  142.     if ((fp = fopen (rcsfile, "r")) == NULL)
  143.     {
  144.     error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
  145.     return (NULL);
  146.     }
  147.  
  148.     /* make a node */
  149.     rdata = (RCSNode *) xmalloc (sizeof (RCSNode));
  150.     bzero ((char *) rdata, sizeof (RCSNode));
  151.     rdata->refcount = 1;
  152.     rdata->path = xstrdup (rcsfile);
  153.     rdata->versions = getlist ();
  154.     rdata->dates = getlist ();
  155.  
  156.     /*
  157.      * process all the special header information, break out when we get to
  158.      * the first revision delta
  159.      */
  160.     for (;;)
  161.     {
  162.     /* get the next key/value pair */
  163.  
  164.     /* if key is NULL here, then the file is missing some headers */
  165.     if (getrcskey (fp, &key, &value) == -1 || key == NULL)
  166.     {
  167.         if (!really_quiet)
  168.         error (0, 0, "`%s' does not appear to be a valid rcs file",
  169.                rcsfile);
  170.         freercsnode (&rdata);
  171.         (void) fclose (fp);
  172.         return (NULL);
  173.     }
  174.  
  175.     /* process it */
  176.     if (strcmp (RCSHEAD, key) == 0 && value != NULL)
  177.     {
  178.         rdata->head = xstrdup (value);
  179.         continue;
  180.     }
  181.     if (strcmp (RCSBRANCH, key) == 0 && value != NULL)
  182.     {
  183.         rdata->branch = xstrdup (value);
  184.         if ((numdots (rdata->branch) & 1) != 0)
  185.         {
  186.         /* turn it into a branch if it's a revision */
  187.         cp = rindex (rdata->branch, '.');
  188.         *cp = '\0';
  189.         }
  190.         continue;
  191.     }
  192.     if (strcmp (RCSSYMBOLS, key) == 0)
  193.     {
  194.         if (value != NULL)
  195.         {
  196.         /* if there are tags, set up the tag list */
  197.         rdata->symbols = getlist ();
  198.         do_symbols (rdata->symbols, value);
  199.         continue;
  200.         }
  201.     }
  202.  
  203.     /*
  204.      * check key for '.''s and digits (probably a rev) if it is a
  205.      * revision, we are done with the headers and are down to the
  206.      * revision deltas, so we break out of the loop
  207.      */
  208.     for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
  209.          /* do nothing */ ;
  210.     if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
  211.         break;
  212.  
  213.     /* if we haven't grabbed it yet, we didn't want it */
  214.     }
  215.  
  216.     /*
  217.      * we got out of the loop, so we have the first part of the first
  218.      * revision delta in our hand key=the revision and value=the date key and
  219.      * its value
  220.      */
  221.     for (;;)
  222.     {
  223.     char *valp;
  224.     char date[MAXDATELEN];
  225.  
  226.     /* grab the value of the date from value */
  227.     valp = value + strlen (RCSDATE);/* skip the "date" keyword */
  228.     while (isspace (*valp))        /* take space off front of value */
  229.         valp++;
  230.     (void) strcpy (date, valp);
  231.  
  232.     /* get the nodes (q is by version, r is by date) */
  233.     q = getnode ();
  234.     r = getnode ();
  235.     q->type = RCSVERS;
  236.     r->type = RCSVERS;
  237.     q->delproc = rcsvers_delproc;
  238.     r->delproc = null_delproc;
  239.     q->data = r->data = xmalloc (sizeof (RCSVers));
  240.     bzero (q->data, sizeof (RCSVers));
  241.     vnode = (RCSVers *) q->data;
  242.  
  243.     /* fill in the version before we forget it */
  244.     q->key = vnode->version = xstrdup (key);
  245.  
  246.     /* throw away the author field */
  247.     (void) getrcskey (fp, &key, &value);
  248.  
  249.     /* throw away the state field */
  250.     (void) getrcskey (fp, &key, &value);
  251.  
  252.     /* fill in the date field */
  253.     r->key = vnode->date = xstrdup (date);
  254.  
  255.     /* fill in the branch list (if any branches exist) */
  256.     (void) getrcskey (fp, &key, &value);
  257.     if (value != (char *) NULL)
  258.     {
  259.         vnode->branches = getlist ();
  260.         do_branches (vnode->branches, value);
  261.     }
  262.  
  263.     /* fill in the next field if there is a next revision */
  264.     (void) getrcskey (fp, &key, &value);
  265.     if (value != (char *) NULL)
  266.         vnode->next = xstrdup (value);
  267.  
  268.     /*
  269.      * at this point, we skip any user defined fields XXX - this is where
  270.      * we put the symbolic link stuff???
  271.      */
  272.     while ((n = getrcskey (fp, &key, &value)) >= 0)
  273.     {
  274.         /* if we have a revision, break and do it */
  275.         for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
  276.          /* do nothing */ ;
  277.         if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
  278.         break;
  279.     }
  280.  
  281.     /* add the nodes to the lists */
  282.     (void) addnode (rdata->versions, q);
  283.     (void) addnode (rdata->dates, r);
  284.  
  285.     /*
  286.      * if we left the loop because there were no more keys, we break out
  287.      * of the revision processing loop
  288.      */
  289.     if (n < 0)
  290.         break;
  291.     }
  292.  
  293.     (void) fclose (fp);
  294.     return (rdata);
  295. }
  296.  
  297. /*
  298.  * rcsnode_delproc - free up an RCS type node
  299.  */
  300. static void
  301. rcsnode_delproc (p)
  302.     Node *p;
  303. {
  304.     freercsnode ((RCSNode **) & p->data);
  305. }
  306.  
  307. /*
  308.  * freercsnode - free up the info for an RCSNode
  309.  */
  310. void
  311. freercsnode (rnodep)
  312.     RCSNode **rnodep;
  313. {
  314.     if (rnodep == NULL || *rnodep == NULL)
  315.     return;
  316.  
  317.     ((*rnodep)->refcount)--;
  318.     if ((*rnodep)->refcount != 0)
  319.     {
  320.     *rnodep = (RCSNode *) NULL;
  321.     return;
  322.     }
  323.     free ((*rnodep)->path);
  324.     dellist (&(*rnodep)->versions);
  325.     dellist (&(*rnodep)->dates);
  326.     if ((*rnodep)->symbols != (List *) NULL)
  327.     dellist (&(*rnodep)->symbols);
  328.     if ((*rnodep)->head != (char *) NULL)
  329.     free ((*rnodep)->head);
  330.     if ((*rnodep)->branch != (char *) NULL)
  331.     free ((*rnodep)->branch);
  332.     free ((char *) *rnodep);
  333.     *rnodep = (RCSNode *) NULL;
  334. }
  335.  
  336. /*
  337.  * rcsvers_delproc - free up an RCSVers type node
  338.  */
  339. static void
  340. rcsvers_delproc (p)
  341.     Node *p;
  342. {
  343.     RCSVers *rnode;
  344.  
  345.     rnode = (RCSVers *) p->data;
  346.  
  347.     if (rnode->branches != (List *) NULL)
  348.     dellist (&rnode->branches);
  349.     if (rnode->next != (char *) NULL)
  350.     free (rnode->next);
  351.     free ((char *) rnode);
  352. }
  353.  
  354. /*
  355.  * null_delproc - don't free anything since it will be free'd by someone else
  356.  */
  357. /* ARGSUSED */
  358. static void
  359. null_delproc (p)
  360.     Node *p;
  361. {
  362.     /* don't do anything */
  363. }
  364.  
  365. /*
  366.  * getrcskey - fill in the key and value from the rcs file the algorithm is
  367.  *             as follows 
  368.  *
  369.  *    o skip whitespace o fill in key with everything up to next white 
  370.  *      space or semicolon 
  371.  *    o if key == "desc" then key and data are NULL and return -1 
  372.  *    o if key wasn't terminated by a semicolon, skip white space and fill 
  373.  *      in value with everything up to a semicolon o compress all whitespace
  374.  *      down to a single space 
  375.  *    o if a word starts with @, do funky rcs processing
  376.  *    o strip whitespace off end of value or set value to NULL if it empty 
  377.  *    o return 0 since we found something besides "desc"
  378.  */
  379.  
  380. static char *key = NULL;
  381. static int keysize = 0;
  382. static char *value = NULL;
  383. static int valsize = 0;
  384.  
  385. #define ALLOCINCR 1024
  386.  
  387. static int
  388. getrcskey (fp, keyp, valp)
  389.     FILE *fp;
  390.     char **keyp;
  391.     char **valp;
  392. {
  393.     char *cur, *max;
  394.     int c;
  395.     int funky = 0;
  396.     int white = 1;
  397.  
  398.     /* skip leading whitespace */
  399.     while (1)
  400.     {
  401.     c = getc (fp);
  402.     if (c == EOF)
  403.     {
  404.         *keyp = (char *) NULL;
  405.         *valp = (char *) NULL;
  406.         return (-1);
  407.     }
  408.     if (!isspace (c))
  409.         break;
  410.     }
  411.  
  412.     /* fill in key */
  413.     cur = key;
  414.     max = key + keysize;
  415.     while (!isspace (c) && c != ';')
  416.     {
  417.     if (cur < max)
  418.         *cur++ = c;
  419.     else
  420.     {
  421.         key = xrealloc (key, keysize + ALLOCINCR);
  422.         cur = key + keysize;
  423.         keysize += ALLOCINCR;
  424.         max = key + keysize;
  425.         *cur++ = c;
  426.     }
  427.     c = getc (fp);
  428.     if (c == EOF)
  429.     {
  430.         *keyp = (char *) NULL;
  431.         *valp = (char *) NULL;
  432.         return (-1);
  433.     }
  434.     }
  435.     *cur = '\0';
  436.  
  437.     /* if we got "desc", we are done with the file */
  438.     if (strcmp (RCSDESC, key) == 0)
  439.     {
  440.     *keyp = (char *) NULL;
  441.     *valp = (char *) NULL;
  442.     return (-1);
  443.     }
  444.  
  445.     /* if we ended key with a semicolon, there is no value */
  446.     if (c == ';')
  447.     {
  448.     *keyp = key;
  449.     *valp = (char *) NULL;
  450.     return (0);
  451.     }
  452.  
  453.     /* otherwise, there might be a value, so fill it in */
  454.     (void) ungetc (c, fp);
  455.     cur = value;
  456.     max = value + valsize;
  457.  
  458.     /* process the value */
  459.     for (;;)
  460.     {
  461.     /* get a character */
  462.     c = getc (fp);
  463.     if (c == EOF)
  464.     {
  465.         *keyp = (char *) NULL;
  466.         *valp = (char *) NULL;
  467.         return (-1);
  468.     }
  469.  
  470.     /* if we are in funky mode, do the rest of this string */
  471.     if (funky)
  472.     {
  473.  
  474.         /*
  475.          * funky mode processing does the following: o @@ means one @ o
  476.          * all other characters are literal up to a single @ (including
  477.          * ';')
  478.          */
  479.         for (;;)
  480.         {
  481.         if (c == '@')
  482.         {
  483.             c = getc (fp);
  484.             if (c == EOF)
  485.             {
  486.             *keyp = (char *) NULL;
  487.             *valp = (char *) NULL;
  488.             return (-1);
  489.             }
  490.             if (c != '@')
  491.             {
  492.             /* @ followed by non @ turns off funky mode */
  493.             funky = 0;
  494.             break;
  495.             }
  496.             /* otherwise, we already ate one @ so copy the other one */
  497.         }
  498.  
  499.         /* put the character on the value (maybe allocating space) */
  500.         if (cur >= max)
  501.         {
  502.             value = xrealloc (value, valsize + ALLOCINCR);
  503.             cur = value + valsize;
  504.             valsize += ALLOCINCR;
  505.             max = value + valsize;
  506.         }
  507.         *cur++ = c;
  508.         c = getc (fp);
  509.         if (c == EOF)
  510.         {
  511.             *keyp = (char *) NULL;
  512.             *valp = (char *) NULL;
  513.             return (-1);
  514.         }
  515.         }
  516.     }
  517.  
  518.     /* if we got the semi-colon we are done with the entire value */
  519.     if (c == ';')
  520.         break;
  521.  
  522.     /* process the character we got */
  523.     if (white && c == '@')
  524.     {
  525.  
  526.         /*
  527.          * if we are starting a word with an '@', enable funky processing
  528.          */
  529.         white = 0;            /* you can't be funky and white :-) */
  530.         funky = 1;
  531.     }
  532.     else
  533.     {
  534.  
  535.         /*
  536.          * we put the character on the list, compressing all whitespace
  537.          * to a single space
  538.          */
  539.  
  540.         /* whitespace with white set means compress it out */
  541.         if (white && isspace (c))
  542.         continue;
  543.  
  544.         if (isspace (c))
  545.         {
  546.         /* make c a space and set white */
  547.         white = 1;
  548.         c = ' ';
  549.         }
  550.         else
  551.         white = 0;
  552.  
  553.         /* put the char on the end of value (maybe allocating space) */
  554.         if (cur >= max)
  555.         {
  556.         value = xrealloc (value, valsize + ALLOCINCR);
  557.         cur = value + valsize;
  558.         valsize += ALLOCINCR;
  559.         max = value + valsize;
  560.         }
  561.         *cur++ = c;
  562.     }
  563.     }
  564.  
  565.     /* if the last char was white space, take it off */
  566.     if (white && cur != value)
  567.     cur--;
  568.  
  569.     /* terminate the string */
  570.     if (cur)
  571.     *cur = '\0';
  572.  
  573.     /* if the string is empty, make it null */
  574.     if (value && *value != '\0')
  575.     *valp = value;
  576.     else
  577.     *valp = NULL;
  578.     *keyp = key;
  579.     return (0);
  580. }
  581.  
  582. /*
  583.  * process the symbols list of the rcs file
  584.  */
  585. static void
  586. do_symbols (list, val)
  587.     List *list;
  588.     char *val;
  589. {
  590.     Node *p;
  591.     char *cp = val;
  592.     char *tag, *rev;
  593.  
  594.     for (;;)
  595.     {
  596.     /* skip leading whitespace */
  597.     while (isspace (*cp))
  598.         cp++;
  599.  
  600.     /* if we got to the end, we are done */
  601.     if (*cp == '\0')
  602.         break;
  603.  
  604.     /* split it up into tag and rev */
  605.     tag = cp;
  606.     cp = index (cp, ':');
  607.     *cp++ = '\0';
  608.     rev = cp;
  609.     while (!isspace (*cp) && *cp != '\0')
  610.         cp++;
  611.     if (*cp != '\0')
  612.         *cp++ = '\0';
  613.  
  614.     /* make a new node and add it to the list */
  615.     p = getnode ();
  616.     p->key = xstrdup (tag);
  617.     p->data = xstrdup (rev);
  618.     (void) addnode (list, p);
  619.     }
  620. }
  621.  
  622. /*
  623.  * process the branches list of a revision delta
  624.  */
  625. static void
  626. do_branches (list, val)
  627.     List *list;
  628.     char *val;
  629. {
  630.     Node *p;
  631.     char *cp = val;
  632.     char *branch;
  633.  
  634.     for (;;)
  635.     {
  636.     /* skip leading whitespace */
  637.     while (isspace (*cp))
  638.         cp++;
  639.  
  640.     /* if we got to the end, we are done */
  641.     if (*cp == '\0')
  642.         break;
  643.  
  644.     /* find the end of this branch */
  645.     branch = cp;
  646.     while (!isspace (*cp) && *cp != '\0')
  647.         cp++;
  648.     if (*cp != '\0')
  649.         *cp++ = '\0';
  650.  
  651.     /* make a new node and add it to the list */
  652.     p = getnode ();
  653.     p->key = xstrdup (branch);
  654.     (void) addnode (list, p);
  655.     }
  656. }
  657.  
  658. /*
  659.  * Version Number
  660.  * 
  661.  * Returns the requested version number of the RCS file, satisfying tags and/or
  662.  * dates, and walking branches, if necessary.
  663.  * 
  664.  * The result is returned; null-string if error.
  665.  */
  666. char *
  667. RCS_getversion (rcs, tag, date, force_tag_match)
  668.     RCSNode *rcs;
  669.     char *tag;
  670.     char *date;
  671.     int force_tag_match;
  672. {
  673.     /* make sure we have something to look at... */
  674.     if (rcs == NULL)
  675.     return ((char *) NULL);
  676.  
  677.     if (tag && date)
  678.     {
  679.     char *cp, *rev, *tagrev;
  680.  
  681.     /*
  682.      * first lookup the tag; if that works, turn the revision into
  683.      * a branch and lookup the date.
  684.      */
  685.     tagrev = RCS_gettag (rcs, tag, force_tag_match);
  686.     if (tagrev == NULL)
  687.         return ((char *) NULL);
  688.  
  689.     if ((cp = rindex (tagrev, '.')) != NULL)
  690.         *cp = '\0';
  691.     rev = RCS_getdatebranch (rcs, date, tagrev);
  692.     free (tagrev);
  693.     return (rev);
  694.     }
  695.     else if (tag)
  696.     return (RCS_gettag (rcs, tag, force_tag_match));
  697.     else if (date)
  698.     return (RCS_getdate (rcs, date, force_tag_match));
  699.     else
  700.     return (RCS_head (rcs));
  701.  
  702. }
  703.  
  704. /*
  705.  * Find the revision for a specific tag.
  706.  * If force_tag_match is set, return NULL if an exact match is not
  707.  * possible otherwise return RCS_head ().  We are careful to look for
  708.  * and handle "magic" revisions specially.
  709.  * 
  710.  * If the matched tag is a branch tag, find the head of the branch.
  711.  */
  712. char *
  713. RCS_gettag (rcs, tag, force_tag_match)
  714.     RCSNode *rcs;
  715.     char *tag;
  716.     int force_tag_match;
  717. {
  718.     Node *p;
  719.  
  720.     /* make sure we have something to look at... */
  721.     if (rcs == NULL)
  722.     return ((char *) NULL);
  723.  
  724.     /* If tag is "HEAD", special case to get head RCS revision */
  725.     if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0'))
  726.     if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
  727.         return ((char *) NULL);    /* head request for removed file */
  728.     else
  729.         return (RCS_head (rcs));
  730.  
  731.     if (!isdigit (tag[0]))
  732.     {
  733.     /* If we got a symbolic tag, resolve it to a numeric */
  734.     if (rcs == NULL)
  735.         p = NULL;
  736.     else
  737.         p = findnode (rcs->symbols, tag);
  738.     if (p != NULL)
  739.     {
  740.         int dots;
  741.         char *magic, *branch, *cp;
  742.  
  743.         tag = p->data;
  744.  
  745.         /*
  746.          * If this is a magic revision, we turn it into either its
  747.          * physical branch equivalent (if one exists) or into
  748.          * its base revision, which we assume exists.
  749.          */
  750.         dots = numdots (tag);
  751.         if (dots > 2 && (dots & 1) != 0)
  752.         {
  753.         branch = rindex (tag, '.');
  754.         cp = branch++ - 1;
  755.         while (*cp != '.')
  756.             cp--;
  757.  
  758.         /* see if we have .magic-branch. (".0.") */
  759.         magic = xmalloc (strlen (tag) + 1);
  760.         (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
  761.         if (strncmp (magic, cp, strlen (magic)) == 0)
  762.         {
  763.             char *xtag;
  764.  
  765.             /* it's magic.  See if the branch exists */
  766.             *cp = '\0';        /* turn it into a revision */
  767.             xtag = xstrdup (tag);
  768.             *cp = '.';        /* and back again */
  769.             (void) sprintf (magic, "%s.%s", xtag, branch);
  770.             branch = RCS_getbranch (rcs, magic, 1);
  771.             free (magic);
  772.             if (branch != NULL)
  773.             {
  774.             free (xtag);
  775.             return (branch);
  776.             }
  777.             return (xtag);
  778.         }
  779.         free (magic);
  780.         }
  781.     }
  782.     else
  783.     {
  784.         /* The tag wasn't there, so return the head or NULL */
  785.         if (force_tag_match)
  786.         return (NULL);
  787.         else
  788.         return (RCS_head (rcs));
  789.     }
  790.     }
  791.  
  792.     /*
  793.      * numeric tag processing:
  794.      *        1) revision number - just return it
  795.      *        2) branch number   - find head of branch
  796.      */
  797.  
  798.     /* strip trailing dots */
  799.     while (tag[strlen (tag) - 1] == '.')
  800.     tag[strlen (tag) - 1] = '\0';
  801.  
  802.     if ((numdots (tag) & 1) == 0)
  803.     {
  804.     /* we have a branch tag, so we need to walk the branch */
  805.     return (RCS_getbranch (rcs, tag, force_tag_match));
  806.     }
  807.     else
  808.     {
  809.     /* we have a revision tag, so make sure it exists */
  810.     if (rcs == NULL)
  811.         p = NULL;
  812.     else
  813.         p = findnode (rcs->versions, tag);
  814.     if (p != NULL)
  815.         return (xstrdup (tag));
  816.     else
  817.     {
  818.         /* The revision wasn't there, so return the head or NULL */
  819.         if (force_tag_match)
  820.         return (NULL);
  821.         else
  822.         return (RCS_head (rcs));
  823.     }
  824.     }
  825. }
  826.  
  827. /*
  828.  * Return a "magic" revision as a virtual branch off of REV for the RCS file.
  829.  * A "magic" revision is one which is unique in the RCS file.  By unique, I
  830.  * mean we return a revision which:
  831.  *    - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH)
  832.  *    - has a revision component which is not an existing branch off REV
  833.  *    - has a revision component which is not an existing magic revision
  834.  *    - is an even-numbered revision, to avoid conflicts with vendor branches
  835.  * The first point is what makes it "magic".
  836.  *
  837.  * As an example, if we pass in 1.37 as REV, we will look for an existing
  838.  * branch called 1.37.2.  If it did not exist, we would look for an
  839.  * existing symbolic tag with a numeric part equal to 1.37.0.2.  If that
  840.  * didn't exist, then we know that the 1.37.2 branch can be reserved by
  841.  * creating a symbolic tag with 1.37.0.2 as the numeric part.
  842.  *
  843.  * This allows us to fork development with very little overhead -- just a
  844.  * symbolic tag is used in the RCS file.  When a commit is done, a physical
  845.  * branch is dynamically created to hold the new revision.
  846.  *
  847.  * Note: We assume that REV is an RCS revision and not a branch number.
  848.  */
  849. static char *check_rev;
  850. char *
  851. RCS_magicrev (rcs, rev)
  852.     RCSNode *rcs;
  853.     char *rev;
  854. {
  855.     int rev_num;
  856.     char *xrev, *test_branch;
  857.  
  858.     xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */
  859.     check_rev = xrev;
  860.  
  861.     /* only look at even numbered branches */
  862.     for (rev_num = 2; ; rev_num += 2)
  863.     {
  864.     /* see if the physical branch exists */
  865.     (void) sprintf (xrev, "%s.%d", rev, rev_num);
  866.     test_branch = RCS_getbranch (rcs, xrev, 1);
  867.     if (test_branch != NULL)    /* it did, so keep looking */
  868.     {
  869.         free (test_branch);
  870.         continue;
  871.     }
  872.  
  873.     /* now, create a "magic" revision */
  874.     (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num);
  875.  
  876.     /* walk the symbols list to see if a magic one already exists */
  877.     if (walklist (rcs->symbols, checkmagic_proc) != 0)
  878.         continue;
  879.  
  880.     /* we found a free magic branch.  Claim it as ours */
  881.     return (xrev);
  882.     }
  883. }
  884.  
  885. /*
  886.  * walklist proc to look for a match in the symbols list.
  887.  * Returns 0 if the symbol does not match, 1 if it does.
  888.  */
  889. static int
  890. checkmagic_proc (p)
  891.     Node *p;
  892. {
  893.     if (strcmp (check_rev, p->data) == 0)
  894.     return (1);
  895.     else
  896.     return (0);
  897. }
  898.  
  899. /*
  900.  * Returns non-zero if the specified revision number or symbolic tag
  901.  * resolves to a "branch" within the rcs file.  We do take into account
  902.  * any magic branches as well.
  903.  */
  904. int
  905. RCS_isbranch (file, rev, srcfiles)
  906.     char *file;
  907.     char *rev;
  908.     List *srcfiles;
  909. {
  910.     int dots;
  911.     Node *p;
  912.     RCSNode *rcs;
  913.  
  914.     /* numeric revisions are easy -- even number of dots is a branch */
  915.     if (isdigit (*rev))
  916.     return ((numdots (rev) & 1) == 0);
  917.  
  918.     /* assume a revision if you can't find the RCS info */
  919.     p = findnode (srcfiles, file);
  920.     if (p == NULL)
  921.     return (0);
  922.  
  923.     /* now, look for a match in the symbols list */
  924.     rcs = (RCSNode *) p->data;
  925.     p = findnode (rcs->symbols, rev);
  926.     if (p == NULL)
  927.     return (0);
  928.     dots = numdots (p->data);
  929.     if ((dots & 1) == 0)
  930.     return (1);
  931.  
  932.     /* got a symbolic tag match, but it's not a branch; see if it's magic */
  933.     if (dots > 2)
  934.     {
  935.     char *magic;
  936.     char *branch = rindex (p->data, '.');
  937.     char *cp = branch - 1;
  938.     while (*cp != '.')
  939.         cp--;
  940.  
  941.     /* see if we have .magic-branch. (".0.") */
  942.     magic = xmalloc (strlen (p->data) + 1);
  943.     (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
  944.     if (strncmp (magic, cp, strlen (magic)) == 0)
  945.     {
  946.         free (magic);
  947.         return (1);
  948.     }
  949.     free (magic);
  950.     }
  951.     return (0);
  952. }
  953.  
  954. /*
  955.  * Returns a pointer to malloc'ed memory which contains the branch
  956.  * for the specified *symbolic* tag.  Magic branches are handled correctly.
  957.  */
  958. char *
  959. RCS_whatbranch (file, rev, srcfiles)
  960.     char *file;
  961.     char *rev;
  962.     List *srcfiles;
  963. {
  964.     int dots;
  965.     Node *p;
  966.     RCSNode *rcs;
  967.  
  968.     /* assume no branch if you can't find the RCS info */
  969.     p = findnode (srcfiles, file);
  970.     if (p == NULL)
  971.     return ((char *) NULL);
  972.  
  973.     /* now, look for a match in the symbols list */
  974.     rcs = (RCSNode *) p->data;
  975.     p = findnode (rcs->symbols, rev);
  976.     if (p == NULL)
  977.     return ((char *) NULL);
  978.     dots = numdots (p->data);
  979.     if ((dots & 1) == 0)
  980.     return (xstrdup (p->data));
  981.  
  982.     /* got a symbolic tag match, but it's not a branch; see if it's magic */
  983.     if (dots > 2)
  984.     {
  985.     char *magic;
  986.     char *branch = rindex (p->data, '.');
  987.     char *cp = branch++ - 1;
  988.     while (*cp != '.')
  989.         cp--;
  990.  
  991.     /* see if we have .magic-branch. (".0.") */
  992.     magic = xmalloc (strlen (p->data) + 1);
  993.     (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
  994.     if (strncmp (magic, cp, strlen (magic)) == 0)
  995.     {
  996.         /* yep.  it's magic.  now, construct the real branch */
  997.         *cp = '\0';            /* turn it into a revision */
  998.         (void) sprintf (magic, "%s.%s", p->data, branch);
  999.         *cp = '.';            /* and turn it back */
  1000.         return (magic);
  1001.     }
  1002.     free (magic);
  1003.     }
  1004.     return ((char *) NULL);
  1005. }
  1006.  
  1007. /*
  1008.  * Get the head of the specified branch.  If the branch does not exist,
  1009.  * return NULL or RCS_head depending on force_tag_match
  1010.  */
  1011. static char *
  1012. RCS_getbranch (rcs, tag, force_tag_match)
  1013.     RCSNode *rcs;
  1014.     char *tag;
  1015.     int force_tag_match;
  1016. {
  1017.     Node *p, *head;
  1018.     RCSVers *vn;
  1019.     char *xtag;
  1020.     char *nextvers;
  1021.     char *cp;
  1022.  
  1023.     /* make sure we have something to look at... */
  1024.     if (rcs == NULL)
  1025.     return ((char *) NULL);
  1026.  
  1027.     /* find out if the tag contains a dot, or is on the trunk */
  1028.     cp = rindex (tag, '.');
  1029.  
  1030.     /* trunk processing is the special case */
  1031.     if (cp == NULL)
  1032.     {
  1033.     xtag = xmalloc (strlen (tag) + 1 + 1);    /* +1 for an extra . */
  1034.     (void) strcpy (xtag, tag);
  1035.     (void) strcat (xtag, ".");
  1036.     for (cp = rcs->head; cp != NULL;)
  1037.     {
  1038.         if (strncmp (xtag, cp, strlen (xtag)) == 0)
  1039.         break;
  1040.         p = findnode (rcs->versions, cp);
  1041.         if (p == NULL)
  1042.         {
  1043.         free (xtag);
  1044.         if (force_tag_match)
  1045.             return (NULL);
  1046.         else
  1047.             return (RCS_head (rcs));
  1048.         }
  1049.         vn = (RCSVers *) p->data;
  1050.         cp = vn->next;
  1051.     }
  1052.     free (xtag);
  1053.     if (cp == NULL)
  1054.     {
  1055.         if (force_tag_match)
  1056.         return (NULL);
  1057.         else
  1058.         return (RCS_head (rcs));
  1059.     }
  1060.     return (xstrdup (cp));
  1061.     }
  1062.  
  1063.     /* if it had a `.', terminate the string so we have the base revision */
  1064.     *cp = '\0';
  1065.  
  1066.     /* look up the revision this branch is based on */
  1067.     p = findnode (rcs->versions, tag);
  1068.  
  1069.     /* put the . back so we have the branch again */
  1070.     *cp = '.';
  1071.  
  1072.     if (p == NULL)
  1073.     {
  1074.     /* if the base revision didn't exist, return head or NULL */
  1075.     if (force_tag_match)
  1076.         return (NULL);
  1077.     else
  1078.         return (RCS_head (rcs));
  1079.     }
  1080.  
  1081.     /* find the first element of the branch we are looking for */
  1082.     vn = (RCSVers *) p->data;
  1083.     if (vn->branches == NULL)
  1084.     return (NULL);
  1085.     xtag = xmalloc (strlen (tag) + 1 + 1);    /* 1 for the extra '.' */
  1086.     (void) strcpy (xtag, tag);
  1087.     (void) strcat (xtag, ".");
  1088.     head = vn->branches->list;
  1089.     for (p = head->next; p != head; p = p->next)
  1090.     if (strncmp (p->key, xtag, strlen (xtag)) == 0)
  1091.         break;
  1092.     free (xtag);
  1093.  
  1094.     if (p == head)
  1095.     {
  1096.     /* we didn't find a match so return head or NULL */
  1097.     if (force_tag_match)
  1098.         return (NULL);
  1099.     else
  1100.         return (RCS_head (rcs));
  1101.     }
  1102.  
  1103.     /* now walk the next pointers of the branch */
  1104.     nextvers = p->key;
  1105.     do
  1106.     {
  1107.     p = findnode (rcs->versions, nextvers);
  1108.     if (p == NULL)
  1109.     {
  1110.         /* a link in the chain is missing - return head or NULL */
  1111.         if (force_tag_match)
  1112.         return (NULL);
  1113.         else
  1114.         return (RCS_head (rcs));
  1115.     }
  1116.     vn = (RCSVers *) p->data;
  1117.     nextvers = vn->next;
  1118.     } while (nextvers != NULL);
  1119.  
  1120.     /* we have the version in our hand, so go for it */
  1121.     return (xstrdup (vn->version));
  1122. }
  1123.  
  1124. /*
  1125.  * Get the head of the RCS file.  If branch is set, this is the head of the
  1126.  * branch, otherwise the real head
  1127.  */
  1128. char *
  1129. RCS_head (rcs)
  1130.     RCSNode *rcs;
  1131. {
  1132.     /* make sure we have something to look at... */
  1133.     if (rcs == NULL)
  1134.     return ((char *) NULL);
  1135.  
  1136.     if (rcs->branch)
  1137.     return (RCS_getbranch (rcs, rcs->branch, 1));
  1138.  
  1139.     /*
  1140.      * NOTE: we call getbranch with force_tag_match set to avoid any
  1141.      * possibility of recursion
  1142.      */
  1143.     else
  1144.     return (xstrdup (rcs->head));
  1145. }
  1146.  
  1147. /*
  1148.  * Get the most recent revision, based on the supplied date, but use some
  1149.  * funky stuff and follow the vendor branch maybe
  1150.  */
  1151. char *
  1152. RCS_getdate (rcs, date, force_tag_match)
  1153.     RCSNode *rcs;
  1154.     char *date;
  1155.     int force_tag_match;
  1156. {
  1157.     char *cur_rev = NULL;
  1158.     char *retval = NULL;
  1159.     Node *p;
  1160.     RCSVers *vers = NULL;
  1161.  
  1162.     /* make sure we have something to look at... */
  1163.     if (rcs == NULL)
  1164.     return ((char *) NULL);
  1165.  
  1166.     /* if the head is on a branch, try the branch first */
  1167.     if (rcs->branch != NULL)
  1168.     retval = RCS_getdatebranch (rcs, date, rcs->branch);
  1169.  
  1170.     /* if we found a match, we are done */
  1171.     if (retval != NULL)
  1172.     return (retval);
  1173.  
  1174.     /* otherwise if we have a trunk, try it */
  1175.     if (rcs->head)
  1176.     {
  1177.     p = findnode (rcs->versions, rcs->head);
  1178.     while (p != NULL)
  1179.     {
  1180.         /* if the date of this one is before date, take it */
  1181.         vers = (RCSVers *) p->data;
  1182.         if (RCS_datecmp (vers->date, date) <= 0)
  1183.         {
  1184.         cur_rev = vers->version;
  1185.         break;
  1186.         }
  1187.  
  1188.         /* if there is a next version, find the node */
  1189.         if (vers->next != NULL)
  1190.         p = findnode (rcs->versions, vers->next);
  1191.         else
  1192.         p = (Node *) NULL;
  1193.     }
  1194.     }
  1195.  
  1196.     /*
  1197.      * at this point, either we have the revision we want, or we have the
  1198.      * first revision on the trunk (1.1?) in our hands
  1199.      */
  1200.  
  1201.     /* if we found what we're looking for, and it's not 1.1 return it */
  1202.     if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0)
  1203.     return (xstrdup (cur_rev));
  1204.  
  1205.     /* look on the vendor branch */
  1206.     retval = RCS_getdatebranch (rcs, date, CVSBRANCH);
  1207.  
  1208.     /*
  1209.      * if we found a match, return it; otherwise, we return the first
  1210.      * revision on the trunk or NULL depending on force_tag_match and the
  1211.      * date of the first rev
  1212.      */
  1213.     if (retval != NULL)
  1214.     return (retval);
  1215.  
  1216.     if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0)
  1217.     return (xstrdup (vers->version));
  1218.     else
  1219.     return (NULL);
  1220. }
  1221.  
  1222. /*
  1223.  * Look up the last element on a branch that was put in before the specified
  1224.  * date (return the rev or NULL)
  1225.  */
  1226. static char *
  1227. RCS_getdatebranch (rcs, date, branch)
  1228.     RCSNode *rcs;
  1229.     char *date;
  1230.     char *branch;
  1231. {
  1232.     char *cur_rev = NULL;
  1233.     char *cp;
  1234.     char *xbranch, *xrev;
  1235.     Node *p;
  1236.     RCSVers *vers;
  1237.  
  1238.     /* look up the first revision on the branch */
  1239.     xrev = xstrdup (branch);
  1240.     cp = rindex (xrev, '.');
  1241.     if (cp == NULL)
  1242.     {
  1243.     free (xrev);
  1244.     return (NULL);
  1245.     }
  1246.     *cp = '\0';                /* turn it into a revision */
  1247.     p = findnode (rcs->versions, xrev);
  1248.     free (xrev);
  1249.     if (p == NULL)
  1250.     return (NULL);
  1251.     vers = (RCSVers *) p->data;
  1252.  
  1253.     /* if no branches list, return NULL */
  1254.     if (vers->branches == NULL)
  1255.     return (NULL);
  1256.  
  1257.     /* walk the branches list looking for the branch number */
  1258.     xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */
  1259.     (void) strcpy (xbranch, branch);
  1260.     (void) strcat (xbranch, ".");
  1261.     for (p = vers->branches->list->next; p != vers->branches->list; p = p->next)
  1262.     if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
  1263.         break;
  1264.     free (xbranch);
  1265.     if (p == vers->branches->list)
  1266.     return (NULL);
  1267.  
  1268.     p = findnode (rcs->versions, p->key);
  1269.  
  1270.     /* walk the next pointers until you find the end, or the date is too late */
  1271.     while (p != NULL)
  1272.     {
  1273.     vers = (RCSVers *) p->data;
  1274.     if (RCS_datecmp (vers->date, date) <= 0)
  1275.         cur_rev = vers->version;
  1276.     else
  1277.         break;
  1278.  
  1279.     /* if there is a next version, find the node */
  1280.     if (vers->next != NULL)
  1281.         p = findnode (rcs->versions, vers->next);
  1282.     else
  1283.         p = (Node *) NULL;
  1284.     }
  1285.  
  1286.     /* if we found something acceptable, return it - otherwise NULL */
  1287.     if (cur_rev != NULL)
  1288.     return (xstrdup (cur_rev));
  1289.     else
  1290.     return (NULL);
  1291. }
  1292.  
  1293. /*
  1294.  * Compare two dates in RCS format. Beware the change in format on January 1,
  1295.  * 2000, when years go from 2-digit to full format.
  1296.  */
  1297. int
  1298. RCS_datecmp (date1, date2)
  1299.     char *date1, *date2;
  1300. {
  1301.     int length_diff = strlen (date1) - strlen (date2);
  1302.  
  1303.     return (length_diff ? length_diff : strcmp (date1, date2));
  1304. }
  1305.  
  1306. /*
  1307.  * Lookup the specified revision in the ,v file and return, in the date
  1308.  * argument, the date specified for the revision *minus one second*, so that
  1309.  * the logically previous revision will be found later.
  1310.  * 
  1311.  * Returns zero on failure, RCS revision time as a Unix "time_t" on success.
  1312.  */
  1313. time_t
  1314. RCS_getrevtime (rcs, rev, date, fudge)
  1315.     RCSNode *rcs;
  1316.     char *rev;
  1317.     char *date;
  1318.     int fudge;
  1319. {
  1320.     char tdate[MAXDATELEN];
  1321.     struct tm xtm, *ftm;
  1322.     time_t revdate = 0;
  1323.     Node *p;
  1324.     RCSVers *vers;
  1325.  
  1326.     /* make sure we have something to look at... */
  1327.     if (rcs == NULL)
  1328.     return (revdate);
  1329.  
  1330.     /* look up the revision */
  1331.     p = findnode (rcs->versions, rev);
  1332.     if (p == NULL)
  1333.     return (-1);
  1334.     vers = (RCSVers *) p->data;
  1335.  
  1336.     /* split up the date */
  1337.     ftm = &xtm;
  1338.     (void) sscanf (vers->date, SDATEFORM, &ftm->tm_year, &ftm->tm_mon,
  1339.            &ftm->tm_mday, &ftm->tm_hour, &ftm->tm_min,
  1340.            &ftm->tm_sec);
  1341.     if (ftm->tm_year > 1900)
  1342.     ftm->tm_year -= 1900;
  1343.  
  1344.     /* put the date in a form getdate can grok */
  1345. #ifdef HAVE_RCS5
  1346.     (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon,
  1347.             ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
  1348.             ftm->tm_min, ftm->tm_sec);
  1349. #else
  1350.     (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon,
  1351.             ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
  1352.             ftm->tm_min, ftm->tm_sec);
  1353. #endif
  1354.  
  1355.     /* turn it into seconds since the epoch */
  1356.     revdate = get_date (tdate, (struct timeb *) NULL);
  1357.     if (revdate != (time_t) -1)
  1358.     {
  1359.     revdate -= fudge;        /* remove "fudge" seconds */
  1360.     if (date)
  1361.     {
  1362.         /* put an appropriate string into ``date'' if we were given one */
  1363. #ifdef HAVE_RCS5
  1364.         ftm = gmtime (&revdate);
  1365. #else
  1366.         ftm = localtime (&revdate);
  1367. #endif
  1368.         (void) sprintf (date, DATEFORM,
  1369.                 ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
  1370.                 ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
  1371.                 ftm->tm_min, ftm->tm_sec);
  1372.     }
  1373.     }
  1374.     return (revdate);
  1375. }
  1376.  
  1377. /*
  1378.  * The argument ARG is the getopt remainder of the -k option specified on the
  1379.  * command line.  This function returns malloc'ed space that can be used
  1380.  * directly in calls to RCS V5, with the -k flag munged correctly.
  1381.  */
  1382. char *
  1383. RCS_check_kflag (arg)
  1384.     char *arg;
  1385. {
  1386.     static char *kflags[] =
  1387.     {"kv", "kvl", "k", "v", "o", (char *) NULL};
  1388.     char karg[10];
  1389.     char **cpp = NULL;
  1390.  
  1391. #ifndef HAVE_RCS5
  1392.     error (1, 0, "%s %s: your version of RCS does not support the -k option",
  1393.        program_name, command_name);
  1394. #endif
  1395.  
  1396.     if (arg)
  1397.     {
  1398.     for (cpp = kflags; *cpp != NULL; cpp++)
  1399.     {
  1400.         if (strcmp (arg, *cpp) == 0)
  1401.         break;
  1402.     }
  1403.     }
  1404.  
  1405.     if (arg == NULL || *cpp == NULL)
  1406.     {
  1407.     (void) fprintf (stderr, "%s %s: invalid -k option\n",
  1408.             program_name, command_name);
  1409.     (void) fprintf (stderr, "\tvalid options are:\n");
  1410.     for (cpp = kflags; *cpp != NULL; cpp++)
  1411.         (void) fprintf (stderr, "\t\t-k%s\n", *cpp);
  1412.     error (1, 0, "Please retry with a valid -k option");
  1413.     }
  1414.  
  1415.     (void) sprintf (karg, "-k%s", *cpp);
  1416.     return (xstrdup (karg));
  1417. }
  1418.  
  1419. /*
  1420.  * Do some consistency checks on the symbolic tag... These should equate
  1421.  * pretty close to what RCS checks, though I don't know for certain.
  1422.  */
  1423. void
  1424. RCS_check_tag (tag)
  1425.     char *tag;
  1426. {
  1427.     char *invalid = "$,.:;@";        /* invalid RCS tag characters */
  1428.     char *cp;
  1429.  
  1430.     /*
  1431.      * The first character must be an alphabetic letter. The remaining
  1432.      * characters cannot be non-visible graphic characters, and must not be
  1433.      * in the set of "invalid" RCS identifier characters.
  1434.      */
  1435.     if (isalpha (*tag))
  1436.     {
  1437.     for (cp = tag; *cp; cp++)
  1438.     {
  1439.         if (!isgraph (*cp))
  1440.         error (1, 0, "tag `%s' has non-visible graphic characters",
  1441.                tag);
  1442.         if (index (invalid, *cp))
  1443.         error (1, 0, "tag `%s' must not contain the characters `%s'",
  1444.                tag, invalid);
  1445.     }
  1446.     }
  1447.     else
  1448.     error (1, 0, "tag `%s' must start with a letter", tag);
  1449. }
  1450.